// ===================================
// <copyright>
// This file is part of LABSMFController.
//
//    LABSMFController is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    LABSMFController is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with LABSMFController.  If not, see <http://www.gnu.org/licenses/>.
//    Copyright 2011 David Bakker
//
//</copyright>
// <author>David Bakker</author>
// <email>mailto:DavidFBakker@gmail.com</email>
// <created>Saturday, May 07, 2011</created>
// <lastedit>Saturday, May 07, 2011</lastedit>
// ===================================

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using GHIElectronics.NETMF.FEZ;
using LABSMFController.LABS.Classses;
using LABSMFController.LABS.Devices;

namespace LABSMFController.LABS.Services
{
    internal class SocketsService : BeerService
    {
        
        private Thread _myServerSocketThread;

        
        public override void Configure()
        {
        
        }

        public override void Start()
        {
        
            _myServerSocketThread = new Thread(SocketServiceThread);
            _myServerSocketThread.Start();
        }

        public override void Stop()
        {
            if (_myServerSocketThread != null)
                _myServerSocketThread.Abort();
        }

        private static void SocketServiceThread()
        {
            const Int32 cPort = 9000;

            var server = new Socket(AddressFamily.InterNetwork,
                                    SocketType.Stream, ProtocolType.Tcp);
            var localEndPoint = new IPEndPoint(IPAddress.Any, cPort);
            server.Bind(localEndPoint);
            server.Listen(Int32.MaxValue);

            while (true)
            {
        
                Socket clientSocket = server.Accept();

                new ProcessClientRequest(clientSocket, true);
            }
// ReSharper disable FunctionNeverReturns
        }
// ReSharper restore FunctionNeverReturns
    }

    /// <summary>
    /// Processes a client request.
    /// </summary>
    internal sealed class ProcessClientRequest
    {
        private readonly Socket _mClientSocket;
        private bool _echo;

        /// <summary>
        /// The constructor calls another method to handle the request, but can 
        /// optionally do so in a new thread.
        /// </summary>
        /// <param name="clientSocket"></param>
        /// <param name="asynchronously"></param>
        public ProcessClientRequest(Socket clientSocket, Boolean asynchronously)
        {
            _echo = false;
            _mClientSocket = clientSocket;

            if (asynchronously)
                // Spawn a new thread to handle the request.
                new Thread(ProcessRequest).Start();
            else ProcessRequest();
        }

        /// <summary>
        /// Processes the request.
        /// </summary>
        private void ProcessRequest()
        {
            const Int32 cMicrosecondsPerSecond = 1000000;

            // 'using' ensures that the client's socket gets closed.
            using (_mClientSocket)
            {
                // Wait for the client request to start to arrive.
                var buffer = new Byte[1024];

                if (_mClientSocket.Poll(5*cMicrosecondsPerSecond, SelectMode.SelectRead))
                {
                    // If 0 bytes in buffer, then the connection has been closed, 
                    // reset, or terminated.
                    if (_mClientSocket.Available == 0)
                        return;

                    string line = "";
                    string oldCommand = "";

                    while (true)
                    {
                        Int32 bytesRead = _mClientSocket.Receive(buffer, _mClientSocket.Available, SocketFlags.None);
                        // if (echo && bytesRead > 0) m_clientSocket.Send(buffer);

                        if (bytesRead == 3 && buffer[0] == 27)
                        {
                            if (buffer[2] == 65)
                            {
                                SendToClient(oldCommand);
                                if (ProccessCommands(oldCommand))
                                {
                                    _mClientSocket.Close();
                                    return;
                                }
                            }
                        }
                        else
                        {
                            for (int c = 0; c < bytesRead; c++)
                            {
                                if (buffer[c] == 10) continue;

                                if (buffer[c] == 13 && ! line.Equals(""))
                                {
                                    if (ProccessCommands(line))
                                    {
                                        _mClientSocket.Close();
                                        return;
                                    }
                                    oldCommand = line;
                                    line = "";
                                }
                                else
                                {                                    
                                    var b = new byte[1];
                                    b[0] = buffer[c];
                                    if (b[0] == 8 && line.Length> 0 )
                                    {
                                        line = line.Substring(0, line.Length - 1);
                                    }
                                    else
                                    {
                                        char[] s = Encoding.UTF8.GetChars(b);
                                        line += s[0];
                                    }
                                }
                            }
                        }
                        Thread.Sleep(100);
                    }
                }
            }
        }

        private bool ProccessCommands(string command)
        {
            if (command == null || command.Equals("")) return false;
            bool exit = false;
            string[] args = command.Split(' ');
            switch (args[0])
            {
                case "hello":
                    _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + DateTime.Now + "\r\n"));
                    break;
                case "help":
                    SendToClient("hello");
                    SendToClient("exit");
                    SendToClient("doheater");
                    SendToClient("dostep");
                    SendToClient("echo");
                    SendToClient("reset");
                    SendToClient("dovalve");
                    SendToClient("set");
                    SendToClient("resethl");
                    SendToClient("dovvalve");
                    SendToClient("list");
                    SendToClient("allstepsoff");
                    SendToClient("turnonrelay");
                    SendToClient("turnoffrelay");
                    SendToClient("lm");
                    SendToClient("dopwm");
                    SendToClient("getangle");
                    SendToClient("autoconfigure");

                    break;
                case "exit":
                    exit = true;
                    break;
                case "doheater":
                    SetHeater(args);
                    break;
                case "turnonrelay":
                    DoTurnOnRelay(args);
                    break;
                case "turnoffrelay":
                    DoTurnOffRelay(args);
                    break;
                case "dostep":
                    DoSteps(args);
                    break;
                case "echo":
                    _echo = !_echo;
                    break;
                case "reset":
                    BeerDevices.ResetDevices();
                    break;
                case "dovalve":
                    DoValve(args);
                    break;
                case "set":
                    DoSet(args);
                    break;
                case "resethl":
                    ResetHighLow();
                    break;
                case "dovvalve":
                    DoVValve(args);
                    break;
                case "list":
                    DoList(args);
                    break;
                case "allstepsoff":
                    AllStepsOff();
                    break;

                case "dopwm":
                    DoPWM(args);
                    break;

                case "getangle":
                    GetAngle(args);
                    break;
                case "autoconfigure":
                    DoAutoConfigure(args);
                    break;
             
             
            }
            return exit;
        }

        private void DoAutoConfigure(String[] args)
        {
            if (args.Length != 2) return;
            try
            {
                string classToAutoConfigure = args[1];
                BeerProgram.AutoConfigure(classToAutoConfigure);
                
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void GetAngle(String[] args)
        {
            if (args.Length != 2) return;
            try
            {
                int pwmnumber = Convert.ToInt32(args[1]);

                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + BeerProgram.GetPWMValveAngle(pwmnumber) + "\r\n"));
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }
    

        private void DoPWM(String[] args)
        {
            if (args.Length != 3) return;
            try
            {
                int pwmnumber = Convert.ToInt32(args[1]);
                int angle = Convert.ToInt32(args[2]);
                

                BeerProgram.SetPWMValveAngle(pwmnumber,angle);
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }
    
        private void AllStepsOff()
        {
            for (int c = 0; c < LiquidMovementUtil.MAXSteps; c++)
                BeerProgram.StopStep(c);

         
            var args = new string[2];
            args[1] = "steps";
            DoList(args);
        }

        private void DoTurnOnRelay(String[] args)
        {
            if (args.Length != 2) return;
            try
            {
                int relaynum = Convert.ToInt32(args[1]);
                
                var relay = FEZ_Components.Relay16.GetRelayFromNum(relaynum);

              
                BeerProgram.TurnOnRelay(relay);
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void DoTurnOffRelay(String[] args)
        {
            if (args.Length != 2) return;
            try
            {
                int relaynum = Convert.ToInt32(args[1]);
                var relay = FEZ_Components.Relay16.GetRelayFromNum(relaynum);
                BeerProgram.TurnOffRelay(relay);
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void DoList(String[] args)
        {
            if (args.Length != 2) return;
            try
            {
                switch (args[1])
                {
                    case "help":
                        SendToClient("steps");
                        SendToClient("levels");
                        SendToClient("temps");
                        SendToClient("valves");
                        SendToClient("heaters");
                        break;
                    case "steps":
                        for (int c = 0; c < LiquidMovementUtil.MAXSteps; c++)
                            SendToClient(c + " " + LiquidMovementUtil.LiquidMovements[c].Description + " IsActive " +
                                         LiquidMovementUtil.LiquidMovements[c].IsActive);
                        break;
                    case "levels":
                        for (int c = 0; c < BubblerServiceClass.MAXBubblers; c++)
                            SendToClient(BeerProgram.GetDescription(typeof (BubblerDevice), c) + " " +
                                         BeerProgram.GetLevel(c));
                        break;
                    case "temps":
                        for (int c = 0; c < TemperatureServiceClass.MAXProbes; c++)
                            SendToClient(BeerProgram.GetDescription(typeof(TempProbe), c) + " " +
                                         BeerProgram.TemperatureService.TempProbes[c].LocationID + " " + BeerProgram.GetTemp(c));
                        break;
                    case "valves":
                        for (int c = 0; c < ValveDevices.MAXValves; c++)
                            SendToClient(ValveDevices.Valves[c].Description + " IsOn " + ValveDevices.Valves[c].IsOn);
                        break;
                    case "vvalves":
                        for (int c = 0; c < VValveDevices.MAXVValves; c++)
                            SendToClient(VValveDevices.VValves[c].Description + " " + VValveDevices.VValves[c].Swing);
                        break;
                    case "heaters":
                        for (int c = 0; c < HeaterServiceClass.MAXHeaters; c++)
                            SendToClient(BeerProgram.HeaterService.Heaters[c].Description + " " +
                                         BeerProgram.HeaterService.Heaters[c].LocationID);
                        break;
                }
            }
            catch (Exception ex)
            {
                SendToClient(ex.Message);
            }
        }

        private void DoVValve(String[] args)
        {
            if (args.Length != 3) return;
            try
            {
                int vvalvenum = Convert.ToInt32(args[1]);
                int level = -64*1024;

                if (args[2] != null)
                {
                    try
                    {
                        level = Convert.ToInt32(args[2]);
                    }
                    catch (Exception ex)
                    {
                        _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                        return;
                    }
                }

                if (level != -64*1024)
                {
                    BeerProgram.VVTurnLevel(vvalvenum, level);
                }
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }


        private void SendToClient(string message, bool needReturn = true)
        {
            _mClientSocket.Send(needReturn ? Encoding.UTF8.GetBytes(message + "\r\n") : Encoding.UTF8.GetBytes(message));
        }


        private static void ResetHighLow()
        {
            for (int c = 0; c < BubblerServiceClass.MAXBubblers; c++)
            {
                BeerProgram.ResetLevel(c);
            }

            for (int c = 0; c < TemperatureServiceClass.MAXProbes; c++)
            {
                BeerProgram.ResetTemps(c);
            }
        }

        private void DoSet(String[] args)
        {
            if (args.Length != 3) return;
            try
            {
                if (Settings.GetSetting(args[1]) != null)
                {
                    for (int index = 0; index < Settings.BeerSettings.Length; index++)
                    {
                        var s = Settings.BeerSettings[index];
                        if (s.Name.Equals(args[1]))
                        {
                            s.Value = args[2];
                            switch (args[1])
                            {
                                case "HLTLevelEmpty":
                                    BeerProgram.SetLevelEmpty(Location.HLT, Convert.ToInt32(args[2]));
                                    break;
                                    //case "HLTLevelStep":
                                    //    for (var c = 0; c < MAXBubblers; c++)
                                    //    {
                                    //        if (BeerDevices.Bubblers[c].LocationID == Location.HLT)
                                    //        {
                                    //            BeerDevices.Bubblers[c].LevelStep = Convert.ToInt32(args[2]);
                                    //            break;
                                    //        }
                                    //    }
                                    //    break;

                                case "MLTLevelEmpty":
                                    BeerProgram.SetLevelEmpty(Location.MLT, Convert.ToInt32(args[2]));
                                    break;
                                    //case "MLTLevelStep":
                                    //    for (var c = 0; c < MAXBubblers; c++)
                                    //    {
                                    //        if (BeerDevices.Bubblers[c].LocationID==Location.MLT)
                                    //        {
                                    //            BeerDevices.Bubblers[c].LevelStep = Convert.ToInt32(args[2]);
                                    //            break;
                                    //        }
                                    //    }
                                    //    break;

                                case "BKLevelEmpty":
                                    BeerProgram.SetLevelEmpty(Location.BK, Convert.ToInt32(args[2]));
                                    break;
                                    //case "BKLevelStep":
                                    //    for (var c = 0; c < MAXBubblers; c++)
                                    //    {
                                    //        if (BeerDevices.Bubblers[c].LocationID==Location.BK)
                                    //        {
                                    //            BeerDevices.Bubblers[c].LevelStep = Convert.ToInt32(args[2]);
                                    //            break;
                                    //        }
                                    //    }
                                    //    break;
                            }

                            return;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void DoValve(String[] args)
        {
            if (args.Length != 3) return;
            try
            {
                int valvenum = Convert.ToInt32(args[1]);
                bool run = false;
                if (args[2].Equals("True") || args[2].Equals("true"))
                {
                    run = true;
                }
                if (run)
                {
                    BeerProgram.ValveOn(valvenum);
                }
                else
                {
                    BeerProgram.ValveOff(valvenum);
                }
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void DoSteps(String[] args)
        {
            if (args.Length != 5) return;
            try
            {
                int stepnum = Convert.ToInt32(args[1]);
                bool run = false;

                try
                {
                    double sd = Convert.ToDouble(args[2]);
                    double dd = Convert.ToDouble(args[3]);
                    LiquidMovementUtil.GetLiquidMovementClass(stepnum).SourceBubblerGallonsToMove = sd;
                    LiquidMovementUtil.GetLiquidMovementClass(stepnum).DestinationBubblerGallons = dd;
                }
                catch (Exception)
                {
                    return;
                }


                if (args[4].Equals("True") || args[4].Equals("true"))
                {
                    run = true;
                }


                if (run)
                {
                    BeerProgram.StartStep(stepnum);
                }
                else
                {
                    BeerProgram.StopStep(stepnum);
                }
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }

        private void SetHeater(String[] args)
        {
            if (args.Length != 4) return;

            try
            {
                int heaternum = Convert.ToInt32(args[1]);


                if (heaternum == -1)
                {
                    _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + "Bad heater or probe name" + "\r\n"));
                    return;
                }

                int temp = Convert.ToInt32(args[2]);
                bool run = false;

                if (args[3].Equals("True") || args[4].Equals("true"))
                {
                    run = true;
                }

                if (run)
                {
                    BeerProgram.StartHeater(heaternum, temp);
                }
                else
                {
                    BeerProgram.StopHeater(heaternum);
                }
            }
            catch (Exception ex)
            {
                _mClientSocket.Send(Encoding.UTF8.GetBytes("\r\n" + ex.Message + "\r\n"));
                return;
            }
        }
    }
}